//
// Created by LiuYou on 2021/6/18.
//

/*
 * 我们要把数据结构隐藏到实现文件中, 不希望用户看到。
 *
 * 使用线程重构。
 */


#include <cerrno>
#include <unistd.h>
#include <algorithm>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include "MyTBF.hpp"


static struct mytbf_st* job[MYTBF_MAX];
static pthread_mutex_t mut_job = PTHREAD_MUTEX_INITIALIZER;
static pthread_t tid_alrm{};
static pthread_once_t init_once = PTHREAD_ONCE_INIT;

static int inited = 0;

// TODO 注意: 这我这个环境中下面这一行代码不写也是可以的！
//typedef void (* sighandler_t)(int);

//static sighandler_t alrm_handler_save;

/**
 * @brief 令牌桶的具体实现。
 */
struct mytbf_st {
    int cps;
    int burst;
    int token;
    int pos;
    pthread_mutex_t mutex;
    pthread_cond_t cond;
};


// TODO 注意: 这个名字是约定俗成的。
//  提示用户在使用这个函数的时候先加锁再调用这个函数。
static int get_free_pos_unlocked() {
    for (int i = 0; i < MYTBF_MAX; ++i) {
        if (job[i] == nullptr) {
            return i;
        }
    }
    return -1;
}

//static void alrm_handler(int s) {
//    alarm(1);
//    for (int i = 0; i < MYTBF_MAX; ++i) {
//        if (job[i] != nullptr) {
//            job[i]->token += job[i]->cps;
//            if (job[i]->token > job[i]->burst) {
//                job[i]->token = job[i]->burst;
//            }
//        }
//    }
//}

static void* thr_alrm(void* pVoid) {
//    alarm(1);

    while (true) {
        pthread_mutex_lock(&mut_job);
        for (int i = 0; i < MYTBF_MAX; ++i) {
            if (job[i] != nullptr) {
                pthread_mutex_lock(&job[i]->mutex);
                job[i]->token += job[i]->cps;
                if (job[i]->token > job[i]->burst) {
                    job[i]->token = job[i]->burst;
                }
                pthread_cond_broadcast(&job[i]->cond);
                pthread_mutex_unlock(&job[i]->mutex);
            }
        }
        pthread_mutex_unlock(&mut_job);
        // TODO 注意: 这里可以要将sleep()函数替换为其它时间相关的函数保证移植性。
        sleep(1);
    }

    pthread_exit(nullptr);
}


/**
 * @brief 作用: 发出第一个信号。
 */
static void module_load() {
//    alrm_handler_save = signal(SIGALRM, alrm_handler);
//    alarm(1);


    int err = pthread_create(&tid_alrm, nullptr, thr_alrm, nullptr);
    if (err) {
        std::fprintf(stderr, "pthread_create(): %s\n", std::strerror(err));
        std::exit(EXIT_FAILURE);
    }

    // TODO 注意: 设计重点！
    //  使用钩子函数。
    std::atexit(module_load);
}


static void module_unload() {
//    signal(SIGALRM, alrm_handler_save);
//    alarm(0);


    pthread_cancel(tid_alrm);
    pthread_join(tid_alrm, nullptr);


    for (int i = 0; i < MYTBF_MAX; ++i) {
        if (job[i] != nullptr) {
            mytbf_destroy(job[i]);
        }
//        // TODO 注意: free()函数如果参数是 void* , 那么
//        //  free()函数不会出错, free()一个 void* 是没有什么影响的。
//        free(job[i]);
    }

    pthread_mutex_destroy(&mut_job);
}

/*
 * TODO 注意: 动态模块的单次初始化函数。
 *   pthread_once()
 *
 */
mytbf_t* mytbf_init(int cps, int burst) {

//    // TODO 注意: 设计上的重点！
//    if (!inited) {
////        signal(SIGALRM,);
////        alarm(1);
//        // TODO 注意: 设计上的重点！
//        module_load();
//        inited = 1;
//    }

    pthread_once(&init_once, module_load);


    mytbf_st* me = static_cast<mytbf_st*>(malloc(sizeof(*me)));
    if (me == nullptr) {
        pthread_mutex_unlock(&mut_job);
        free(me);
        return nullptr;
    }
    me->token = 0;
    me->cps = cps;
    me->burst = burst;
    pthread_mutex_init(&me->mutex, nullptr);
    pthread_cond_init(&me->cond, nullptr);

    pthread_mutex_lock(&mut_job);
    int pos = get_free_pos_unlocked();
    if (pos < 0) {
        return nullptr;
    }

    me->pos = pos;

    job[pos] = me;
    pthread_mutex_unlock(&mut_job);

    return me;
}


// TODO 注意: 这个版本是一个盲等的版本。之后会改进它。
//  通过条件变量将查询法改为了通知法, CPU的占用率不会到接近100%了。
int mytbf_fetchtoken(mytbf_t* ptr, int size) {
    mytbf_st* me = static_cast<mytbf_st*>(ptr);
    if (size <= 0) {
        // TODO 注意: 巧妙使用了 cerrno 。
        return -EINVAL;
    }


//    // TODO 注意: CPU的一个核占满, 程序出现盲等现象。原因在这里:
//    //  这是典型的查询法(轮询法)。 举个例子: CPU会在这里一秒钟执行上亿次。
//    pthread_mutex_lock(&me->mutex);
//    // TODO 注意: 是 while 而不是 if。
//    while (me->token <= 0) {
////        pause();
//        pthread_mutex_unlock(&me->mutex);
//        sched_yield();
//        pthread_mutex_lock(&me->mutex);
//    }

    // TODO 注意: 改进
    // TODO 注意: CPU的一个核占满, 程序出现盲等现象。原因在这里:
    //  这是典型的查询法(轮询法)。 举个例子: CPU会在这里一秒钟执行上亿次。
    pthread_mutex_lock(&me->mutex);
    // TODO 注意: 是 while 而不是 if。
    while (me->token <= 0) {
//        pthread_mutex_unlock(&me->mutex);
//        sched_yield();
//        pthread_mutex_lock(&me->mutex);
        pthread_cond_wait(&me->cond, &me->mutex);
    }

    // TODO 注意: 这里是使用了C++的STL算法。
    //  并没有像慧琴老师那样自己实现了一个 min()函数。
    int minVal = std::min(me->token, size);
    me->token -= minVal;
    pthread_mutex_unlock(&me->mutex);
    return minVal;
}

int mytbf_returntoken(mytbf_t* ptr, int size) {
    if (size <= 0) {
        return -EINVAL;
    }
    mytbf_st* me = static_cast<mytbf_st*>(ptr);

    pthread_mutex_lock(&me->mutex);
    me->token += size;
    if (me->token > me->burst) {
        me->token = me->burst;
    }
    pthread_cond_broadcast(&me->cond);
    pthread_mutex_unlock(&me->mutex);
    return size;
}

int mytbf_destroy(mytbf_t* ptr) {
    mytbf_st* me = static_cast<mytbf_st*>(ptr);

    pthread_mutex_lock(&mut_job);

    // TODO 注意: 设计上的重点！
    job[me->pos] = nullptr;
    pthread_mutex_unlock(&mut_job);
    pthread_mutex_destroy(&me->mutex);
    pthread_cond_destroy(&me->cond);
    free(ptr);

    return 0;
}
